<?php
class Tpl
{
    private $arrAssign = array(); // 赋值
    private $strTplDir, $strCompiledDir, $strLeft, $strRight; // 配置
    
    /**
     * 构造函数
     * @param type $strTplDir
     * @param type $strCompiledDir
     * @param type $strLeft
     * @param type $strRight
     */
    public function __construct($strTplDir, $strCompiledDir, $strLeft = '{{', $strRight = '}}')
    {
        $this->strTplDir = rtrim($strTplDir, '/');
        $this->strCompiledDir = rtrim($strCompiledDir, '/');
        $this->strLeft = $strLeft;
        $this->strRight = $strRight;
    }
    
    /**
     * 赋值
     * @param type $strVar
     * @param type $strValue
     */
    public function assign($strVar, $strValue = null)
    {
        if (is_string($strVar) && strlen($strVar))
        {
            $this->arrAssign[$strVar] = $strValue;
        }
        elseif (is_array($strVar) && count($strVar))
        {
            foreach ($strVar as $key => $val)
            {
                $this->assign($key, $val);
            }
        }
    }
    
    /**
     * 获取模板编译后的内容
     * @param type $strTplFile
     * @return string $str
     */
    private function getCompileString($strTplFile)
    {
        $str = File::read($strTplFile);
        if (strlen($str))
        {
            $left = $this->strLeft;
            $right = $this->strRight;
            /* include */
            $str = preg_replace_callback('/' . $left . 'include\s+[\'|\"]([^{]+?)[\'|\"]' . $right . '/', array(&$this, 'callback4include'), $str);
            /* if操作 */
            $str = preg_replace("/" . $left . "if\s+([^{]+?)" . $right . "/", "<?php if (\\1) { ?>", $str);
            $str = preg_replace("/" . $left . "else" . $right . "/", "<?php } else { ?>", $str);
            $str = preg_replace("/" . $left . "elseif\s+([^{]+?)" . $right . "/", "<?php } elseif (\\1) { ?>", $str);
            $str = preg_replace("/" . $left . "\/if" . $right . "/", "<?php } ?>", $str);
            /* foreach操作 */
            $str = preg_replace("/" . $left . "foreach\s+([^{]+?)" . $right . "/", "<?php foreach (\\1) { ?>", $str);
            $str = preg_replace("/" . $left . "\/foreach" . $right . "/", "<?php } ?>", $str);
            /* for操作 */
            $str = preg_replace("/" . $left . "for\s+([^{]+?)" . $right . "/","<?php for (\\1) { ?>", $str);
            $str = preg_replace("/" . $left . "\/for" . $right . "/", "<?php } ?>", $str);
            /* 输出变量 */
            $str = preg_replace("/" . $left . "(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_$\x7f-\xff\[\]\'\']*)" . $right . "/", "<?php isset(\\1) && print(\\1); ?>", $str);
            /* 常量输出 */
            $str = preg_replace("/" . $left . "([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)" . $right . "/s", "<?php defined('\\1') && print(\\1); ?>", $str);
            /* PHP标签 */
            $str = preg_replace(array('/' . $left . '/', '/' . $right . '/'), array('<?php ', ' ?>'), $str);
        }
        // $str .= "\n<!-- " . basename($strTplFile) . " compiled @ " . date('Y-m-d H:i:s') . " -->\n"; // 调试
        return $str;
    }
    
    /**
     * 处理模板包含
     * @param type $arrMatch
     * @return string
     */
    private function callback4include($arrMatch)
    {
        $re = '';
        if (is_array($arrMatch) && count($arrMatch) == 2)
        {
            $strFile = trim($arrMatch[1]);
            if (strlen($strFile))
            {
                $re = <<<CODE
<?php
\$strCompiledIncFile = Tpl::getCompiledIncFile(\$__TPL_CURRENT__, '{$strFile}');
if (\$strCompiledIncFile) {
    include(\$strCompiledIncFile);
} elseif (is_file('{$strFile}')) {
    include('{$strFile}');
}
?>
CODE;
            }
        }
        return $re;
    }
    
    /**
     * 检查包含的模板
     * @param Tpl $objTpl
     * @param type $strTpl
     * @param type $strCompileFile
     * @return boolean or string
     */
    public static function getCompiledIncFile(&$objTpl, $strTpl)
    {
        $strRe = false;
        if (isset($objTpl) && $objTpl instanceof Tpl)
        {
            $strRe = $objTpl->getCompiledFile($strTpl); // 检查被包含的模板是否要重新编译
        }
        return $strRe;
    }

    /**
     * 取编译文件
     * @param string $strTpl
     * @return string
     */
    private function getCompiledFile($strTpl)
    {
        $re = false;
        $strTpl = ltrim($strTpl, '/');
        $strTplFile = $this->strTplDir . '/' . $strTpl;
        if (is_file($strTplFile))
        {
            $strCompiledFile = $this->strCompiledDir . '/' . $strTpl;
            /* 编译文件不存在或过期，需要重新生成 */
            if (!is_file($strCompiledFile) || filemtime($strCompiledFile) < filemtime($strTplFile))
            {
                File::write($strCompiledFile, $this->getCompileString($strTplFile));
            }
            $re = $strCompiledFile;
        }
        else
        {
            print('Warning: template file "' . $strTpl . '" does NOT exists!');
        }
        return $re;
    }
    
    /**
     * 取页面内容
     * @param type $strTpl
     * @param type $arrAssign
     * @return $strRe
     */
    public function fetch($strTpl, $arrAssign = null)
    {
        $strRe = '';
        $strCompiledFile = $this->getCompiledFile($strTpl);
        if ($strCompiledFile && is_file($strCompiledFile))
        {
            $this->assign($arrAssign);
            ob_start();
            extract($this->arrAssign);
            $this->arrAssign = array();
            $__TPL_CURRENT__ = &$this; // 当前实例
            include($strCompiledFile);
            $strRe = ob_get_contents();
            ob_end_clean();
        }
        return $strRe;
    }
    
    /**
     * 显示页面
     * @param type $strTpl
     * @param type $arrAssign
     */
    public function display($strTpl, $arrAssign = null)
    {
        print($this->fetch($strTpl, $arrAssign));
    }
}
